//
//  Statuz.m
//  Statz
//
//  Created by Dave MacLachlan on 07/08/03.
//
// Copyright 2007 Google Inc.
// 
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.
//

#import "Status.h"
#import "StatzSource.h"
#import "AppDelegate.h"
#import "StatzTextAttachment.h"

const int kStatzStatusCoderVersion = 1;
static NSString *const kStatzStatusCoderVersionKey = @"kStatzStatusCoderVersionKey";
static NSString *const kStatzStatusStatusKey = @"kStatzStatusStatusKey";
const unichar kZeroSpaceUniChar = 0x200B;

@interface Status (StatusPrivate) 
- (NSMutableAttributedString*)expandedAttributedStatus;
- (NSMutableAttributedString*)expandedAttributedStatus:(NSMutableArray*)seenStatuses;
@end

@implementation Status

+ (id)coerceStatus:(Status*)status toStringClass:(Class)stringClass {
  return [stringClass stringWithString:[status status]];
}

+ (id)coerceString:(NSString*)string toStatusClass:(Class)statusClass {
  return [statusClass statusWithString:string];
}

+ (id)status:(NSAttributedString*)status {
  return [[[[self class] alloc] initWithStatus:status] autorelease];
}

+ (id)statusWithString:(NSString*)status {
  NSAttributedString *attrStatus = [[[NSAttributedString alloc] initWithString:status] autorelease];
  return [self status:attrStatus];
}

+ (NSString*)newStatusString {
  return NSLocalizedString(@"New Status", @"New Status String");
}

- (id)copyWithZone:(NSZone *)zone {
  return [[[self class] allocWithZone:zone] initWithStatus:[self attributedStatus]];
}

- (id)init {
  if ((self = [super init])) {
    NSString *untitledStatusString = [[self class] newStatusString];
    NSAttributedString *untitled = [[NSAttributedString alloc] initWithString:untitledStatusString];
    [self setAttributedStatus:untitled];
    [untitled release];
  }
  return self;
}


- (id)initWithStatus:(NSAttributedString*)status {
  if ((self = [super init])) {
    [self setAttributedStatus:status];
  }
  return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  if ((self = [super init])) {
    int version = [aDecoder decodeInt32ForKey:kStatzStatusCoderVersionKey];
    if (version != kStatzStatusCoderVersion) {
      [self release];
      return nil;
    }
    status_ = [[aDecoder decodeObjectForKey:kStatzStatusStatusKey] retain];
  }
  return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
  [aCoder encodeInt32:kStatzStatusCoderVersion forKey:kStatzStatusCoderVersionKey];
  [aCoder encodeObject:status_ forKey:kStatzStatusStatusKey];
}

- (void)dealloc {
  [status_ release];
  [super dealloc];
}


- (NSAttributedString *)attributedStatus {
  return [[status_ retain] autorelease];
}

- (void)setAttributedStatus:(NSAttributedString *)value {
  if (status_ != value) {
    [status_ autorelease];
    NSMutableAttributedString *mutable = [value mutableCopy];
    if (([mutable length] == 1) && 
        ([[mutable string] characterAtIndex:0] == NSAttachmentCharacter)) {
      
      [mutable replaceCharactersInRange:NSMakeRange(0,0) 
                                 withString:[NSString stringWithCharacters:&kZeroSpaceUniChar 
                                                                    length:1]];
    } else if (([mutable length] > 1) && 
               ([[mutable string] characterAtIndex:0] == kZeroSpaceUniChar) &&
               ([[mutable string] characterAtIndex:1] == NSAttachmentCharacter)) {
      [mutable deleteCharactersInRange:NSMakeRange(0,1)];
    }
    [mutable beginEditing];
    [mutable addAttribute:NSFontAttributeName 
                    value:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]
                    range:NSMakeRange(0, [mutable length])];
    [mutable endEditing];
    status_ = mutable;
    
  }
}

- (BOOL)isEqual:(id)object {
  Status *inStatuz = object;
  return [inStatuz isMemberOfClass:[Status class]] &&
    [[inStatuz attributedStatus] isEqualTo:[self attributedStatus]];
}

- (unsigned)hash {
  return [[self attributedStatus] hash];
}

- (BOOL)isEqualStatus:(id)object {
  Status *inStatuz = object;
  return [inStatuz isMemberOfClass:[Status class]] &&
  [[inStatuz status] isEqualTo:[self status]];
}

- (NSString*)description {
  return [self status];
}

- (NSMutableAttributedString*)expandedAttributedStatus:(NSMutableArray*)seenStatuses {
  if (!seenStatuses) return nil;
  [seenStatuses addObject:self];
  NSMutableAttributedString *evalString = [[[self attributedStatus] mutableCopy] autorelease];
  NSString *string = [evalString string];
  unichar attachmentChar = NSAttachmentCharacter;
  NSString *attachmentString = [NSString stringWithCharacters:&attachmentChar length:1];
  NSRange searchRange = NSMakeRange(0, [string length]);
  while (searchRange.length != 0) {
    NSRange foundRange = [string rangeOfString:attachmentString 
                                       options:NSBackwardsSearch 
                                         range:searchRange];
    if (foundRange.length == 0) break;
    searchRange.length = foundRange.location;
    NSDictionary *attributes = [evalString attributesAtIndex:foundRange.location
                                              effectiveRange:nil];
    StatzTextAttachment *attachment = [attributes objectForKey:NSAttachmentAttributeName];
    StatzSource *source = [attachment statusSource];
    Status *status = [source currentStatus];
    if (status && ![seenStatuses containsObject:status]) {
      NSAttributedString *expandedStatus = [status expandedAttributedStatus];
      if (expandedStatus) {
        [evalString replaceCharactersInRange:foundRange 
                        withAttributedString:expandedStatus];
      }
    }
  }
  return evalString;
}
  
- (NSMutableAttributedString*)expandedAttributedStatus {
  return [self expandedAttributedStatus:[NSMutableArray array]];
}

- (NSString *)status {
  NSMutableAttributedString *expandedStatus = [self expandedAttributedStatus];
  NSMutableString *status = [expandedStatus mutableString];
  if (([status length] > 0) 
      && ([status characterAtIndex:0] == kZeroSpaceUniChar)) {
    [status deleteCharactersInRange:NSMakeRange(0,1)];
  }
  return status;
}

- (void)setStatus:(NSString*)status {
  NSAttributedString *aStatus = [[NSAttributedString alloc] initWithString:status];
  [self setAttributedStatus:aStatus];
  [aStatus release];
} 

- (NSString *)toolTip {
  NSString *tip = nil;
  NSAttributedString *status = [self attributedStatus];
  NSMutableAttributedString *expandedStatus = [self expandedAttributedStatus];
  if (![status isEqualToAttributedString:expandedStatus]) {
    tip = [expandedStatus string];
  }
  return tip;
}

- (void)addSourceDependenciesTo:(NSMutableArray*)array {
  NSAttributedString *attributedStatus = [self attributedStatus];
  NSString *string = [attributedStatus string];
  unichar attachmentChar = NSAttachmentCharacter;
  NSString *attachmentString = [NSString stringWithCharacters:&attachmentChar length:1];
  NSRange searchRange = NSMakeRange(0, [string length]);
  while (searchRange.length != 0) {
    NSRange foundRange = [string rangeOfString:attachmentString 
                                       options:NSBackwardsSearch 
                                         range:searchRange];
    if (foundRange.length == 0) break;
    searchRange.length = foundRange.location;
    NSDictionary *attributes = [attributedStatus attributesAtIndex:foundRange.location
                                                    effectiveRange:nil];
    StatzTextAttachment *attachment = [attributes objectForKey:NSAttachmentAttributeName];
    StatzSource *source = [attachment statusSource];
    if ([array indexOfObject:source] == NSNotFound) {
      [array addObject:source];
      [source addSourceDependenciesTo:array];
    }
  }
}

- (NSScriptObjectSpecifier *)objectSpecifier {
  AppDelegate *delegate = [NSApp delegate];
  NSEnumerator *e = [[delegate statusSources] objectEnumerator];
  StatzSource *source = nil;
  while ((source = [e nextObject])) {
    NSArray *statuses = [source statuses];
    unsigned int objectIndex = [statuses indexOfObject:self];
    if (objectIndex != NSNotFound) {
      NSScriptClassDescription *containerClassDesc = (NSScriptClassDescription *)
        [NSScriptClassDescription classDescriptionForClass:[source class]];
      return [[[NSIndexSpecifier alloc] initWithContainerClassDescription:containerClassDesc
                                                       containerSpecifier:[source objectSpecifier]
                                                                      key:@"statuses"
                                                                    index:objectIndex] autorelease];
    }
  }
  return nil;
}

@end
